package gr.aueb.cs.mscis.chordroid.chord;

import gr.aueb.cs.mscis.chordroid.ChordroidService;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import android.util.Log;


/**
 * This class implements the node of our system
 * @author lupin
 *
 */
public class Node implements Runnable, Serializable {

	private static final long serialVersionUID = 1L;
	private NodeIdentifier successor;
	private NodeIdentifier predecessor;
	private String key;
	private NodeIdentifier nodeId;
	private RemoteMessage msg;
	private RemoteMessageDaemon rdaemon;
	private List <NodeIdentifier>successorList;
	

	public Node() {
		bootStrap();		
	}

	/**
	 * The bootStrap Method. Creates a BootstrapService that sends a register
	 * request to the Tracker. The Node's RemoteMessageDaemon is also created
	 * and listens to a port that the tracker specified. At, last the Node
	 * either creates a new Chord , or it joins an existing one  
	 */
	private void bootStrap() {
	
		// bootstrapping
		BootstrapService boot = new BootstrapService();
		msg = boot.register();
		setNodeId(msg.getNodeId());
		key = nodeId.getHash();
		successorList=new ArrayList<NodeIdentifier>();
		
		// instantiate the Daemon thread
		rdaemon = new RemoteMessageDaemon(msg.getNodeId().getPort());
		rdaemon.setNode(this);
		Thread deamon = new Thread(rdaemon);
		deamon.start();

		// check the message from the tracker
		// and act analogously
		if (msg.getProtocolType().equals(ProtocolType.JOIN_CHORD)) {
			join((NodeIdentifier) msg.getPayload());
		} else if (msg.getProtocolType().equals(ProtocolType.CREATE_CHORD)) {
			create();
		}
	}

	/**
	 * In case there is noone in our system a new chord is created
	 */
	public void create() {
		successor = this.getNodeId();
		successorList.add(successor);
		predecessor = null;
	}

	/**
	 * In case there is already a node registered in the tracker
	 * join the chord system
	 * @param remote
	 */
	public void join(NodeIdentifier remote) {
		successor=getNodeId();
		successorList.add(successor);
		predecessor = null;
		// the successor is set from the accessor method
		remoteFindSuccesor(remote, this.getNodeId().getHash(), this.getNodeId());
	}

	/**
	 * Calls Agent for a recursive call of findSuccessor 
	 * @param remote
	 * @param key
	 * @param node
	 */
	private synchronized void remoteFindSuccesor(NodeIdentifier remote, String key, NodeIdentifier node) {
		msg.setNodeId(node);
		msg.setPayload(key);
		msg.setProtocolType(ProtocolType.FIND_SUCCESSOR);
		RemoteMessageAgent.sendRemoteMessage(msg, remote);
	}

	/**
	 * Stabilize method
	 */
	private synchronized void stabilize() {
		// check if remote is required
		msg.setNodeId(nodeId);
		msg.setPayload(null);
		msg.setProtocolType(ProtocolType.GET_PREDECESSOR);
				
		RemoteMessage rm = RemoteMessageAgent.sendSynchronousRemoteMessage(msg, getSuccessor());
		//elegxoume an einai epityxhs h epikoinwnia
		if (rm == null) return;
		NodeIdentifier x = (NodeIdentifier) rm.getPayload();

		if (x != null) { // the first time
			if (betweenMeAndMySucc(x.getHash())) {
				setSuccessor(x);
			}
		}
		remoteNotify(getSuccessor());
	}
	
	public void notify(NodeIdentifier remote) {
		if (getPredecessor() == null || betweenPredAndMe(remote.getHash())) {
			setPredecessor(remote);
		}
	}

	/**
	 * Is K between me and my successor ?
	 * @param k
	 * @return
	 */
	private boolean betweenMeAndMySucc(String k) {
		if ((this.getKey().compareTo(k) < 0)
				&& (k.compareTo(this.getSuccessor().getHash()) < 0)) {
			return true;
		}
		if ((this.getKey().compareTo(k) < 0)
				&& (this.getSuccessor().getHash().compareTo(this.getKey()) < 0)) {
			return true;
		}
		if ((this.getSuccessor().getHash().compareTo(this.getKey()) < 0)
				&& (k.compareTo(this.getSuccessor().getHash()) < 0)) {
			return true;
		} else {
			return false;
		}
	}
	
	/**
	 * Is K between my predecessor and me ?
	 * @param k
	 * @return
	 */
	private boolean betweenPredAndMe(String k) {
		if ((this.getPredecessor().getHash().compareTo(k) < 0)
				&& (k.compareTo(this.getKey()) < 0)) {
			return true;
		}
		if ((this.getPredecessor().getHash().compareTo(k) < 0)
				&& (this.getKey().compareTo(this.getPredecessor().getHash()) < 0)) {
			return true;
		}
		if ((this.getKey().compareTo(this.getPredecessor().getHash()) < 0)
				&& (k.compareTo(this.getKey()) < 0)) {
			return true;
		} else {
			return false;
		}
	}

	/**
	 * This method calls the notify method of another node
	 */
	private void remoteNotify(NodeIdentifier remote) {
		msg.setNodeId(this.nodeId);
		msg.setPayload(null);
		msg.setProtocolType(ProtocolType.NOTIFY);
		
		RemoteMessageAgent.sendRemoteMessage(msg, remote);
	}
	

	/**
	 * the findSuccessor() method
	 * @param shaKey
	 * @param nodeId
	 */
	public synchronized void findSuccessor(String shaKey, NodeIdentifier nodeId) {
		// IF the key is in between current node and it's successor
		// OR if this.successor == this.nodeId (I'm the first node)
		// then return this.successor
		// ELSE call remoteFindSuccesor
		if (betweenMeAndMySucc(shaKey) || this.getKey().equals(this.getSuccessor().getHash())) {

			// if a Node requests its successor
			if (nodeId.getHash().equals(shaKey)) {
				msg.setNodeId(nodeId);
				msg.setPayload(this.getSuccessor());
				msg.setProtocolType(ProtocolType.FIND_SUCCESSOR_RESULT);
			}
			else { // if a Node's App requests the successor of a file/chunk
				msg.setNodeId(this.getSuccessor());
				msg.setPayload(shaKey);
				msg.setProtocolType(ProtocolType.FIND_SUCCESSOR_RESULT_FOR_APP);
			}
			RemoteMessageAgent.sendRemoteMessage(msg, nodeId);
		} else {
			remoteFindSuccesor(getSuccessor(), shaKey, nodeId);
		}

	}
	
	/**
	 * The node leaves the Chord System
	 */
	public void leave() {
        //if this is the last node in chord
        if (getSuccessor()==null || getSuccessor()==getNodeId()) return;

		// inform predecessor
		msg.setNodeId(getNodeId());
		msg.setPayload(getSuccessor());
		msg.setProtocolType(ProtocolType.LEAVE_CHORD);
		RemoteMessageAgent.sendRemoteMessage(msg, getPredecessor());
		
		// inform successor
		if (!getSuccessor().equals(getPredecessor())){
			msg.setNodeId(getNodeId());
			msg.setPayload(getPredecessor());
			msg.setProtocolType(ProtocolType.LEAVE_CHORD);
			RemoteMessageAgent.sendRemoteMessage(msg, getSuccessor());
		}
		Log.v("Node", "leaving chord...");
		
		ChordroidService.getDht().leave();
		
	}
		
	private synchronized void remoteGetSuccessorList(){
		msg.setNodeId(this.nodeId);
		msg.setPayload(null);
		msg.setProtocolType(ProtocolType.GET_SUCCESSOR_LIST);
		
		RemoteMessage rm=RemoteMessageAgent.sendSynchronousRemoteMessage(msg, successor);
		//de to poluskefthka ayto..:D
		if (rm!=null){ updateSuccessorList((List<NodeIdentifier>)rm.getPayload());}
		
		
	} 

	/**
	 * The run method calls periodically the Chord's stabilize method
	 */
	public void run() {
		for (int i=0;i>=0;i++) {
			try {
				Thread.sleep(1500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			//no reason to stabilize 
			if (successor != this.nodeId) {
				stabilize();
			}
			//in case of the first node, check if a new node changed your predecessor
			if (this.getNodeId().getHash().equals(this.successor.getHash()) && getPredecessor() != null) 
			{
				this.setSuccessor(getPredecessor());
				Log.v("run", "chord cycle closed!");
			}
			//periodically update the successorsList
			if (i==1) {
				remoteGetSuccessorList();
				i=0;
			}
			
		}
	}

	public NodeIdentifier getSuccessor() {
		return successor;
	}

	/**
	 * Sets a successor, accessed from the Daemon
	 * @param successor
	 */
	public void setSuccessor(NodeIdentifier successor) {
		this.successor = successor;
	}

	public NodeIdentifier getPredecessor() {
		return predecessor;
	}

	public void setPredecessor(NodeIdentifier predecessor) {
		this.predecessor = predecessor;
	}

	public String getKey() {
		return key;
	}

	public NodeIdentifier getNodeId() {
		return nodeId;
	}

	public void setNodeId(NodeIdentifier nodeId) {
		this.nodeId = nodeId;
	}

	public List<NodeIdentifier> getSuccessorList() {
		return successorList;
	}
	
	/**
	 * updates successors list. This method is called from the RemoteMessageDaemon
	 * which got a call from current node's successor with it's node list
	 */
	public void updateSuccessorList(List<NodeIdentifier> resultList) {
		this.successorList.clear();
		this.successorList.addAll(resultList);
		this.successorList.add(0,successor);
	}



}